package br.com.spam.pipeline.phase;

import java.util.Collections;
import java.util.Iterator;
import java.util.Map.Entry;

import br.com.spam.pipeline.core.ControlSignal;
import br.com.spam.pipeline.instruction.Instruction;
import br.com.spam.pipeline.instruction.InstructionPhase;
import br.com.spam.pipeline.instruction.r.NOP;

public class Phase
{
	private final PipelinePhase phaseType;
	private Instruction currentInstruction;
	private boolean finished;
	private boolean bubble;
	private int bubbleCount;

	public Phase(PipelinePhase phase)
	{
		this.phaseType = phase;
		this.reset();
	}

	public String getPhaseAsString()
	{
		return this.phaseType.name();
	}

	public void reset()
	{
		this.currentInstruction = new NOP(null);
		this.finished = false;
		this.bubble = false;
		this.bubbleCount = 0;
	}

	public void setCurrentInstruction(Instruction instruction)
	{
		if (!this.isAvailable())
			throw new RuntimeException("Tentou colocar nova instrução sem acabar a anterior: "
					+ this.getPhaseAsString());

		this.reset();

		this.currentInstruction = instruction;
	}

	public Instruction takeLastInstruction()
	{
		if (!this.isFinished())
			throw new RuntimeException("Tentou obter a instrução antes do término");

		Instruction temp = this.currentInstruction;
		this.reset();
		return temp;
	}

	public void clockUp(InstructionPhase phase)
	{
		if (phase.equals(InstructionPhase.SECOND_HALF))
		{
			if (this.phaseType.equals(PipelinePhase.ID_RF))
			{
				this.bubble = !this.currentInstruction.readRegisters();
			}
		}
		else
		{
			if (this.finished)
			{
				++this.bubbleCount;
			}
			else if (this.bubble)
			{
				++this.bubbleCount;
			}
			else
			{
				this.finished = this.executeCorrectMethod();
			}
		}
	}

	private boolean executeCorrectMethod()
	{
		switch (this.phaseType)
		{
			case EX :
				return this.currentInstruction.execute();
			case ID_RF :
				return this.currentInstruction.instructionDecode();
			case IF :
				return this.currentInstruction.instructionFetch();
			case MEM :
				return this.currentInstruction.memory();
			case WB :
				return this.currentInstruction.writeBack();
			default :
				return false;
		}
	}

	public boolean isAvailable()
	{
		return this.currentInstruction instanceof NOP;
	}

	public boolean isFinished()
	{
		return this.finished && !this.bubble;
	}

	public boolean isBusy()
	{
		return !(this.currentInstruction instanceof NOP || this.bubbleCount >= 1);
	}

	public PipelinePhase getPhaseType()
	{
		return this.phaseType;
	}

	public Iterable<Entry<ControlSignal, Object>> getControlSignals()
	{
		if (this.bubble)
		{
			return new NOP(null).getControlSignals();
		}
		else if (this.getPhaseType().equals(PipelinePhase.IF))
		{
			return Collections.emptyList();
		}
		else
		{
			return this.getControlSignalsCore();
		}
	}

	private Iterable<Entry<ControlSignal, Object>> getControlSignalsCore()
	{
		final Iterator<Entry<ControlSignal, Object>> baseIterator = this.currentInstruction
				.getControlSignals().iterator();

		final Iterator<Entry<ControlSignal, Object>> iterator = new Iterator<Entry<ControlSignal, Object>>()
		{
			private Entry<ControlSignal, Object> next;

			@Override
			public void remove()
			{
				throw new UnsupportedOperationException();
			}

			@Override
			public Entry<ControlSignal, Object> next()
			{
				Entry<ControlSignal, Object> retVal = this.next;
				this.next = null;
				return retVal;
			}

			@Override
			public boolean hasNext()
			{
				while (baseIterator.hasNext())
				{
					Entry<ControlSignal, Object> candidate = baseIterator.next();
					if (candidate.getKey().getPhase().compareTo(Phase.this.getPhaseType()) >= 0)
					{
						this.next = candidate;
						return true;
					}
				}

				return false;
			}
		};

		return new Iterable<Entry<ControlSignal, Object>>()
		{
			@Override
			public Iterator<Entry<ControlSignal, Object>> iterator()
			{
				return iterator;
			}
		};
	}

	public Instruction getCurrentInstruction()
	{
		return this.currentInstruction;
	}
}
